/*
 * Copyright (c) 2011 Sapienza University of Rome.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * - Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the
 *   distribution.
 * - Neither the name of the Sapienza University of Rome nor the names of
 *   its contributors may be used to endorse or promote products derived
 *   from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL SAPIENZA
 * UNIVERSITY OF ROME OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */
/**
 * This is the Adaptive Engine of DISSense. This component takes as input the sampling period (SP),
 * the time to resynchronize (TTR) and the time to receive data (TTRD). It gives as output the Guard Time Interval (GTI),
 * the Resynchronization Interval (RI), the Data Collection Interval (DCI) and the Skip parameter (see DISSense paper).
 *
 * @author Ugo Colesanti
 * @version 1.02 (September 28, 2011)
 *
 */

module AdaptiveEngineC{
  provides{
	  interface AdaptiveLayer ;
  }
}

implementation{

	static void computeResyncInterval(uint16_t) ;
	static void computePrepareToSleepInterval(uint16_t) ;
	static void computeResyncSkip() ;
	static void computeGuardTime() ;


	static uint8_t computeBestResyncSkip() ;
	static uint16_t dutyCycle(uint16_t skip) ;

	enum{
		ALPHA = 9 ,
		RISING_ALPHA = 7,
		FALLING_ALPHA = 9,
		PPM = 50 ,
		PPM_FACTOR = 3 ,
	};

	uint8_t skip = 0 ;						// adaptive Resync Skip value
	uint8_t countdown_to_resync = 0 ;		// counter to next resync

	uint16_t sampling_period ; 					// sampling period
	uint16_t ri ;				// adaptive resync interval
	uint16_t dci ;	// adaptive prepare to sleep interval

	uint16_t ri_upperbound ;				// upperbound for resync interval
	uint16_t dci_upperbound ;				// upperbound for prepare to sleep interval

	uint16_t guardtime ;					// adaptive guardtime


	uint16_t next_ri = 0 ;

	command void AdaptiveLayer.updateInValues(uint16_t ttr, uint16_t ttrd, uint16_t sp){
//		printf("Updating in values\n") ; // was enabled in august
		sampling_period = sp ;
		if(ttrd != 0) computePrepareToSleepInterval(ttrd) ; 	//1
		computeResyncSkip() ;					//2
		if(ttr != 0) computeResyncInterval(ttr) ;			//3
#ifndef TOSSIM
//		printfflush() ; // was enabled in august
#endif
	}

	command void AdaptiveLayer.activePeriodBegin(){
		if(countdown_to_resync == 0){ // The previous phase was a resync phase, recompute intervals with new values
			ri = next_ri ;	// updated later since this value must be used only for next periods
			computeGuardTime() ;
			countdown_to_resync = skip+1 ;				// reset the countdown
			dbg("AdaptiveLayer",">>>>>>>>>>>>>>>>>>>>>>>End period -- ri=%u , cntdwn=%u\n",ri,countdown_to_resync) ;
		}


		countdown_to_resync-- ;
		dbg("AdaptiveLayer",">>>>>>>>>>>>>>>>>>>>>>>>>>>>Start period -- countdown to resync: %u\n",countdown_to_resync) ;
//		printf("AL - new period (-%u) , RI: %u, DCI: %u, Skip: %u\n",countdown_to_resync,ri,dci,skip) ; // was enabled in august
#ifndef TOSSIM
//		printfflush() ; // was enabled in august
#endif

		if(countdown_to_resync == 0) signal AdaptiveLayer.needInValuesUpdate() ;

	}

	command bool AdaptiveLayer.isResyncScheduled(){
		return (countdown_to_resync == 0) ? TRUE: FALSE ;
	}

	command uint8_t AdaptiveLayer.getCurrentSkip(){
		return skip ;
	}


	command uint16_t AdaptiveLayer.getCurrentGT(){
		return guardtime ;
	}

	/*
	 * NODES FUNCTION
	 * It retrieves the RI set by beacons messages. It is not the same as the sink since there is the safety factor.
	 * Moreover, it is NOT equal to the value returned by the sink's function getTxRI() since the former represents
	 * the value that will be used during the next phase while the latter represents the value that will be used
	 * during the NEXT resync phase.
	 */
	command uint16_t AdaptiveLayer.getCurrentRI(){
		return ri ;
	}

	command uint16_t AdaptiveLayer.getNextRI(){
			return next_ri ;
	}


	command void AdaptiveLayer.storeCurrentSamplingPeriod(uint16_t sp){ // updates the sampling period.
		sampling_period = sp ;
	}


	/*
	 * NODES FUNCTION
	 * It stores the skip value from beacons messages. This value will be used from this period to the next resync phase.
	 */
	command void AdaptiveLayer.storeCurrentSkip(uint8_t value){
		skip = value ;
//		printf("New skip: %u\n",skip) ; // was enabled in august
#ifndef TOSSIM
//		printfflush() ; // was enabled in august
#endif
	}


	/*
	 * NODES FUNCTION
	 * it stores the ri interval from beacons messages. It is not the same as the sink since there is the safety factor.
	 * Moreover, this value is different from the value returned by the sink's function getTxPTSI() since the former
	 * represents the RI to be used during the NEXT resync phase while the latter is the value to be used during THIS phase.
	 */
	command void AdaptiveLayer.storeNextRI(uint16_t value){
		if(value <= ri_upperbound){
			next_ri = value ;
			dbg("AdaptiveLayer","Storing new ri: %u\n",value) ;
//			printf("New RI (temp): %u\n",next_ri) ; // was enabled in august
#ifndef TOSSIM
//			printfflush() ; // was enabled in august
#endif
		}
	}

	/*
	 * NODES FUNCTION
	 * it retrieves the ptsi interval set by beacons messages. It is not the same as the sink since there is the safety factor.
	 * It is equal to the value returned by the sink's function getTxPTSI()
	 */
	command uint16_t AdaptiveLayer.getCurrentDCI(){
		return dci ;
	}

	/*
	 * NODES FUNCTION
	 * it stores the ptsi interval from beacons messages. It is not the same as the sink since there is the safety factor.
	 * It is equal to the value returned by the sink's function getTxPTSI()
	 */
	command void AdaptiveLayer.storeCurrentDCI(uint16_t value){
		if(value <= dci_upperbound){
			dci = value ;
			dbg("AdaptiveLayer","Storing new dci: %u\n",value) ;
//			printf("New Dci: %u\n",dci) ; // was enabled in august
#ifndef TOSSIM
//			printfflush() ; // was enabled in august
#endif
		}
	}


	/*
	 * SINK+NODES FUNCTION
	 * The Guard Time is recomputed when countdown_to_resync becomes 0.
	 * It depends on:
	 * 		- the possibly new sampling interval	-> interval
	 * 		- the possibly new skip value			-> skip
	 * 		- the constant values of drift			-> PPM,FACTOR
	 */
	static void computeGuardTime(){
		uint32_t newGt = sampling_period*1024UL*PPM*PPM_FACTOR*(skip+1) ;
		dbg("AdaptiveLayer","ComputeGuardTime - interval: %u , PPM: %u , FACTOR: %u , SKIP: %u\n",sampling_period,PPM,PPM_FACTOR,skip) ;
		newGt /= 1000000UL  ;
		newGt++ ;
		guardtime = (uint16_t) newGt ;
		dbg("AdaptiveLayer","Recomputed guardtime: %u\n",guardtime) ;
	}



	//------------------------------------- SINK FUNCTIONS ---------------------------------------------------------------

	/*
	 * SINK FUNCTION
	 * It computes the new local prepare_to_sleep interval using an EWMA. The values is directly used in the actual period.
	 */
	static void computePrepareToSleepInterval(uint16_t value){
		dbg("AdaptiveLayer","ComputePrepareToSleepInterval, argument: %u\n",value) ;
		if(value <= dci_upperbound){
			if((value*1.5) < dci) dci = (FALLING_ALPHA*dci + (10-FALLING_ALPHA)*value*1.5) / 10 ;
			else dci = (RISING_ALPHA*dci + (10-RISING_ALPHA)*value*1.5) / 10 ;
			if(dci < 300) dci = 300 ;
			dbg("AdaptiveLayer","After upper_bound check: %u\n",dci) ;
		}
	}

	/*
	 * SINK FUNCTION
	 * It computes the new local resync_interval using an EWMA but do not stores the result directly in the local resync_interval, rather it is stored in
	 * a temporary variable, next_resync_interval. The resync_interval variable is set only at the end of the period and when the countdown_to_resync is 0.
	 */
	static void computeResyncInterval(uint16_t value){
			dbg("AdaptiveLayer","ComputeResyncInterval, argument: %u\n",value) ;
			if(value <= ri_upperbound){
				if((value*1.5) < ri) next_ri = (FALLING_ALPHA*ri + (10-FALLING_ALPHA)*value*1.5) / 10 ;
				else next_ri = (RISING_ALPHA*ri + (10-RISING_ALPHA)*value*1.5) / 10 ;
				if(next_ri < 300) next_ri = 300 ;
				dbg("AdaptiveLayer","After upper_bound check: %u\n",next_ri) ;
			}
	}

	/*
	 * SINK FUNCTION
	 * Compute the best SKIP value s.t. duty cycle is minimized:
	 * 	- Ton = pts								-> lower is better
	 * 	- DeltaResync = 1.5*ri		-> lower is better until...
	 * 	- Tperiod = interval*1024				-> fixed
	 * 	- 1-alpha = 1/(SKIP+1)					-> variable
	 *
	 * 	>>>>> dc = 10^-4 * (SKIP+1) + ttr/[interval*(SKIP+1)] + pts/interval
	 */
	static uint8_t computeBestResyncSkip(){
#ifndef DISABLE_SKIP
		uint32_t numerator = 10UL*((uint32_t)call AdaptiveLayer.getCurrentRI())*10000UL ;
		uint32_t denominator = 15UL*sampling_period*1024UL ;
		uint32_t delta = numerator/denominator ;
		uint16_t sk = 0 ;
		uint16_t dc_floor = 0 ;
		uint16_t dc_roof = 0 ;

		if(delta < 1) delta = 1 ; // the value must be greater than zero!

#if defined(PLATFORM_TELOSB)
		sk = sqrtf(delta) - 1 ;
#elif defined(PLATFORM_MICAZ) || defined(TOSSIM)
		sk = sqrt(delta) - 1 ;
#endif

		dc_floor = dutyCycle(sk) ;		// due to truncation: I check both values
		dc_roof = dutyCycle(sk+1) ;	// due to truncation: I check both values

		dbg("AdaptiveLayer","ComputeBestResyncSkip - delta: %lu , skip: %u , dc_floor: %u , dc_roof: %u\n",delta,sk,dc_floor,dc_roof) ;

		if(dc_floor <= dc_roof) return (uint8_t) sk ;
		else return (uint8_t) sk+1 ;
#else
		return 0 ;
#endif
	}

	/*
	 * SINK FUNCTION
	 * Utility function for skip computation
	 */
	static uint16_t dutyCycle(uint16_t sk){
		uint32_t el1 = 15UL*(sk+1)/10UL + 15UL*(call AdaptiveLayer.getCurrentRI())*1000UL/(sampling_period*1024UL*(sk+1)) + 15UL*(call AdaptiveLayer.getCurrentDCI())*1000UL / (sampling_period*1024UL); //10000 times higher
		return (uint16_t) el1 ;
	}

	/*
	 * SINK FUNCTION
	 * Recomputes the skip value (it must happen only when countdown to resync becomes 0)
	 */
	static void computeResyncSkip(){
			skip = computeBestResyncSkip() ;
	}

	/*
	 * SINK FUNCTION
	 * Called by sink during boot.booted event.
	 */
	command void AdaptiveLayer.initialize(uint8_t ctr, uint16_t ri_up , uint16_t dci_up, uint16_t sp, uint16_t ri_, uint16_t dci_ , uint8_t sk){
		dbg("AdaptiveLayer","----------------- Initialization ------------------\n") ;
		countdown_to_resync = ctr ;
		ri_upperbound = ri_up ;	// will remain constant
		dci_upperbound = dci_up ;	// will remain constant
		sampling_period = sp ; // initial value for sampling period

		if(ri_ == 0) ri = next_ri = ri_upperbound ;				// put the upperbound for SINK (node d.c. since it get resync first time)
		else ri = next_ri = ri_ ;
		if(dci_ == 0) dci = dci_upperbound ;							// put the upperbound for SINK (node d.c. since it get resync first time)
		else dci = dci_ ;

		if(sk == 0 && countdown_to_resync == 0){
			skip = computeBestResyncSkip() ;								// initial value for SKIP (depends on interval, ri and prepare_to_sleep)
		}
		else skip = sk ;
		computeGuardTime() ; // initial value for Guard Time (depends on SKIP , interval)
		dbg("AdaptiveLayer","---------------- END Initialization ----------------\n") ;
	}
}
